package org.NooLab.glue.subscription.context;

import java.util.*;


import org.apache.commons.collections.CollectionUtils;


import org.NooLab.utilities.logging.PrintLog;
import org.NooLab.compare.CompareIntf;
import org.NooLab.compare.utilities.math.DistanceControlProperties;

import org.NooLab.glue.subscription.ContextHeaderDescription;


/**
 * 
 * This class is the entry portal for organizing the comparison of fundamentally different kinds 
 * of data structure, such like sets, vectors, contexts, full text, or images; </br>
 * the comparison itself is performed by an external class "Compare" that implements "CompareIntf"; </br> 
 * </br>
 * This class needs to be instantiated via CompareFactory(), which serves as a broker for the instances;</br>
 * </br>
 * 
 * Else, it provides a method to register individual methods, which would extend the built-in ones
 * 
 * </br>
 * check apache CollectionUtils !!
 * offers things like  intersection() and subtract() 
 * 
 * @author kwa
 *
 */
public class Comparison implements ComparisonIntf{

	
	ComparisonFactory cof;
	// DistanceControlProperties ctrlProperties ;
	
	Vector<String> commonItems = new Vector<String>();
	Vector<String> difference  = new Vector<String>();
	
	CompareResultHandleIntf  compareResultHandle ;
	
	CompareIntf compareInst ;
	PrintLog out;
	
	
	 
	public Comparison( CompareIntf comparekernel, PrintLog outprn ){
	 	
		compareInst = comparekernel;
		
		out = outprn;
	}  
	
	// similarity calculations on feature vectors are not available  
	public Comparison(   PrintLog outprn ){
		
		out = outprn;
	} 

	/**
	 * using this constructor, the CompareKernel has to be set explicitly ("setCompareKernel()")
	 */
	public Comparison(){
		
		out = new PrintLog(2, false);
	}
	
	/**
	 * 
	 */
	public void setCompareKernel( CompareIntf comparekernel ){
		compareInst = comparekernel;
	}
	
	/** 
	 * 
	 */
	public void registerCompareDistanceMethod( DistanceMethodIntf distancemethod ){
		
	}
	 
	
	public CompareResultHandleIntf getLastResultHandle(){
		return compareResultHandle;
	}

	public void setLastResultHandle(CompareResultHandleIntf resulthandle){
		
		compareResultHandle = resulthandle ;
	}
	
	
	
	/**
	 * 
	 */
	public boolean compareSets( DistanceControlProperties dcp, String[] itemset1, String[] itemset2, String[] exclitemset, double threshold) {
	
		boolean rB=false, hb;
		double   matchRatio=0.0;
		Vector<String> itemsx = new Vector<String>();
				
		
		if ((itemset1==null) || (itemset1.length==0) || (itemset2==null) || (itemset2.length==0)){
			return false;
		}
		
		Vector<String> items1 = new Vector<String>( Arrays.asList( itemset1 )) ;
		Vector<String> items2 = new Vector<String>( Arrays.asList( itemset2 )) ;
		
		if ((exclitemset!=null) && (exclitemset.length>0)){
			itemsx = new Vector<String>( Arrays.asList( exclitemset )) ;
			// trying to retain all elements that occur in items1
			itemsx.retainAll(items1) ;
		}
		
		// nothing should be retained == no overlap == no excluding items met
		hb = ((itemsx==null) || (itemsx.size()==0));
		removeEmptyItemsFromList(items1);
		removeEmptyItemsFromList(items2);
		
		if (hb){
			
			if (compareInst==null){
				
			}
			// we will compare the two sets according to the settings that
			// are provided by the DistanceControlProperties
			// (note that the excluding elements already have been checked)
			
			
			compareInst.setDistanceCtrl(dcp );
			// DistanceControlProperties dcp = compareInst.getDistanceCtrl() ;
			// we could adjust/select here even the method to be used .... !
			
			// this will first proceed to Compare{} (via the interface), then it will use 
			// the distance object to calculate the similarity
			matchRatio = compareInst.calculateSimilarity( itemset1, itemset2 ) ;
			 
			
			if ( dcp.getNormalizedDistances() ){
				threshold = threshold/itemset1.length; 
			}
			rB = matchRatio >= threshold ;
			
			publishResultDetails(matchRatio) ;
		}
        
		return rB;
	}
	
	
	/**
	 * 
	 */
	public boolean compareFeatures( DistanceControlProperties dcp, 
		    				        double[] valueset1, double[] valueset2, 
		    				        double simThreshold) {
		
		boolean rB=false ;
		double sdv;
		
		
		try{
			
			compareInst.setDistanceCtrl(dcp );

			// using an imported/interfaced similarity function, 
			// then calculating the similarity of profiles (there will be additional check for different length)
			sdv = compareInst.calculateSimilarity( valueset1, valueset2) ;
			
			if ( dcp.getNormalizedDistances() ){
				simThreshold = simThreshold/valueset1.length; 
			}
			rB = sdv >= simThreshold ;
			
			publishResultDetails(sdv) ;
			
		}catch(Exception e){
			rB = false;
			publishResultState( "critical error in compareFeatures : " + e.getMessage() ) ;
		}
		
		return rB;
	}

	/** 
	 * 
	 */
	public boolean compareFeatures( DistanceControlProperties dcp, 
								    String[] itemset1, double[] valueset1, 
				                	String[] itemset2, double[] valueset2, 
				                	String[] exclitemset,
				                	double matchThreshold, double simThreshold) {
		
		boolean rB=false ;
		double sdv=0.0;
		
		
		// first the sets  
		rB = compareSets( dcp, itemset1, itemset2, exclitemset, matchThreshold);
		 
		if (rB){
			// now we know that the difference in structure (feature sets) is tolerable

			// first: aligning the vectors, also making them equally long
			// yet we check only if it is allowed and necessary
			if (dcp.getBypassVectorAlignCheck()==false){
				
				Alignment align = new Alignment(dcp);
				align.featureVectors( itemset1, valueset1, itemset2, valueset2 , 1) ;
				
				if (align.getSigAdjust()>0){ // it is not complete over there...
					// only if length changed, itemset1 will be a different object
					itemset1 = Arrays.copyOf(align.itemset1, align.itemset1.length) ;
					itemset2 = Arrays.copyOf(align.itemset2, align.itemset2.length) ;

					valueset1 = Arrays.copyOf(align.valueset1, align.valueset1.length) ;
					valueset2 = Arrays.copyOf(align.valueset2, align.valueset2.length) ;
				}
				align=null;				
			}
			
			compareInst.setDistanceCtrl(dcp );

			// using an imported/interfaced similarity function, 
			// then calculating the similarity of profiles (there will be additional check for different length)
			sdv = compareInst.calculateSimilarity( valueset1, valueset2) ;
			
			if ( dcp.getNormalizedDistances() ){
				simThreshold = simThreshold/valueset1.length; 
			}
			rB = sdv >= simThreshold ;
		}
		
		return rB;
	}

	
	public boolean compareText( String fulltext, String[] simpleSet, double threshold ){ // , ContextHeaderDescription contextdescription) {
		boolean rB=false ;
		
		
		

		return rB;
	}
	

	public boolean compareText( String fulltext, ContextHeaderDescription contextdescription){ // , ) {
		boolean rB=false ;
		
		
		

		return rB;
	}
	

	public boolean compareText( String fulltext, String templateText){
		// here we have several possibilities:
		// text-text, text-context, text-item set
		
		boolean rB=false ;
		
		
		

		return rB;
	}
	


	public boolean compareSetToContext( String fulltext, Context context) {

		boolean rB=false ;
		

		return rB;
	}

	

	public boolean compareimages( byte[] binimage, int mode) {

		boolean rB=false ;
		

		return rB;
	}
	
	
	 
	public boolean compareText( Context contextA, Context contextTemplate){
		boolean rB=false ;
		
		

		return rB;
	}
	
	
	private void publishResultDetails( double value ){
	
		if (compareResultHandle==null){
			return;
		}
		//CompareResultHandle crh ;
		compareResultHandle.setSimilarityValue(value);
					
		
	}
	
	private void publishResultState( String stmsg){
	
		compareResultHandle.setLastStatusMessage(stmsg);
	}
	
	
	
	public String[] removeIntersectionOfSetsFrom( int target, String[] set1, String[] set2 ){
		
		String[] sect,citems = new String[0];
		// Set<String> difference;
		Vector<String> cs1 , cs2, rsc ;
		
		if (set1==null){ return null; }
		cs1 = new Vector<String>( Arrays.asList(set1));
		// cs1b = new Vector<String>( Arrays.asList(set1));
		cs2 = new Vector<String>( Arrays.asList(set2));
		
		sect = getIntersectionOfSets( set1, set2);
		
		rsc = new Vector<String>( Arrays.asList(sect));
		
		if (target<=1){
			// difference = new HashSet<String>( rsc );
			// difference.removeAll( cs1 ); // rsc now contains the intersection between cs1
			  
			cs1.removeAll( rsc );
			
		    citems = new String[cs1.size()];
		    cs1.toArray(citems);
		}
		if (target>=2){
			 
			cs2.removeAll(rsc ); 

			citems = new String[cs2.size()];
		    cs2.toArray(citems);
		}
		
		
		return citems;
	}
	
	
	@SuppressWarnings("unchecked")
	public String[] getIntersectionOfSets( String[] set1, String[] set2){
		
		String[] xitems = new String[0];
		Vector<String> rc;
		
		List<String> c1 = Arrays.asList( set1);
		List<String> c2 = Arrays.asList( set2 );
		
		rc = new Vector<String>( CollectionUtils.intersection(  c1,  c2));
		
		xitems = new String[rc.size()];
		for (int i=0;i<rc.size();i++){
			xitems[i] = rc.get(i);
		}
		
		return xitems;
	}

	public String[] getCommonItems(){
		String[] cit ;
		
		cit = new String[commonItems.size()];
		commonItems.toArray(cit);
		
		return cit;
	}
	public String[] getDifferences( int targetSetIndex ){
		String[] dit ;
		
		dit = new String[difference.size()];
		difference.toArray(dit);
		
		return dit;
	}

	
	
	private void removeEmptyItemsFromList( Vector<String> items){
		
		int i = items.size()-1;
		
		while (i>=0){
			if (items.get(i).trim().length()==0){
				items.remove(i);
			}
			i--;
		}
		
	}

	class Alignment{
		
		DistanceControlProperties dcp;
		
		String[] itemset1; 
		String[] itemset2; 
		double[] valueset1; 
		double[] valueset2;
		
		int sigAdjust = 0;
		     
		public Alignment(DistanceControlProperties dcp){
			this.dcp = dcp ;
			
		}
		
		/*
		 * we have different possibilities here:
		 * - first unifying the feature sets and then applying MV
		 * - first intersecting the feature sets
		 * 
		 *  usually we already and independently checked the feature sets, so 
		 *  we may resort to unification + MV 
		 */
		@SuppressWarnings("unchecked")
		public void featureVectors( String[] itemset_1, double[] valueset_1, 
			  						     String[] itemset_2, double[] valueset_2,
				  						 int mode){
		
			itemset1 = itemset_1; 
			itemset2 = itemset_2 ; 
			valueset1 = valueset_1 ; 
			valueset2 =  valueset_2 ;

			int p,q,e ;
			String itemstr, str;
			double v;
			
			Collection<String> clct;
			Vector<String> unions;
			Vector<Double> unionsValues;
			Vector<String> items1 = new Vector<String>( Arrays.asList( itemset1 )) ;
			Vector<String> items2 = new Vector<String>( Arrays.asList( itemset2 )) ;
			
			  			
			try{
				
				if (mode==1){ // unify
					clct = CollectionUtils.union(items1, items2);
					unions = new Vector<String>(clct) ;
					
					// now aligning the data items 
					unionsValues = new Vector<Double>();
					unionsValues.setSize( unions.size()) ;
					// int uvc = unions.size() ;
					// the sets could be equal, but the order still different
					
					// repeat until no edit took place
					e=1;
					while (e>0){
						e=0;
						/*
						 *      a y x 
						 *      x y k
						 */
		
						for (int i=0;i<items1.size();i++){
							itemstr = items1.get(i);
							p = items2.indexOf( itemstr ) ;
		
							// treat only the elements which are common, other elements are inserted anyway
							if ((p>=0) && (p != i)){
								str = items2.get(p); items2.set( p,items2.get(i) ) ; items2.set( i,str ) ;
								
								v   = valueset2[p] ; valueset2[p] = valueset2[i]   ; valueset2[i] = v;
								str = itemset2[p] ; itemset2[p] = itemset2[i] ; itemset2[i] = str;
								
								e++;
							}
							
						} // i->
						
					} // while e>0
					
					for (int i=0;i<items1.size();i++){
						itemstr = items1.get(i);
						p = items2.indexOf( itemstr ) ;
						if (p<0){
							valueset1[i] = -1.0;
						}
					}
					 
					for (int i=0;i<items2.size();i++){
						itemstr = items2.get(i);
						p = items1.indexOf( itemstr ) ;
						if (p<0){
							valueset2[i] = -1.0;
						}
					}
					
					if (itemset1.length != itemset2.length){
						p = itemset1.length;
						q = itemset2.length;
						if (p>q){
							sigAdjust=1;
							
						}else{
							sigAdjust=1;
							
						}
					}
					
				} // mode==1 ?
				
				
				if (mode==2){ // intersect
				
					
					
				} // mode==2 ?
				
			}catch(Exception ex){
				ex.printStackTrace();
			}
			p=0;
		}


		public int getSigAdjust() {
			return sigAdjust;
		}
		
	} // class Alignment


	 

	
	
	
}
















